grammar WhileLang;

options {
	language = C;
	backtrack = true;
	memoize = true;
}

tokens {
	AND			= '&';
	OR			= '|';
	ASSIGN		= ':=';
	SEMI		= ';';
	GT			= '>';
	GE			= '>=';
	LT_fix		= '<';		// ANTLR's C-runtime library is stupid enough to define a macro LT
	LE			= '<=';
	EQ			= '=';
	NEQ			= '!=';
	PLUS		= '+';
	MINUS		= '-';
	MUL			= '*';
	DIV			= '/';
	NOT			= '!';
	LPAREN		= '(';
	RPAREN		= ')';
	LBRACKET	= '[';
	RBRACKET	= ']';
	IF			= 'if';
	THEN		= 'then';
	ELSE		= 'else';
	FI			= 'fi';
	WHILE		= 'while';
	DO			= 'do';
	OD			= 'od';
	SKIPP		= 'skip';	// Rename to SKIPP, since SKIP causes confusion with a SKIP-macro in generated C code
	WRITE		= 'write';
	READ		= 'read';
	PROGRAM		= 'program';
	END			= 'end';
	TRUE		= 'true';
	FALSE		= 'false';
	INT			= 'int';
}

@lexer::header
{
	#define	ANTLR3_INLINE_INPUT_ASCII
}

@parser::includes
{
	#include <cstdlib>
	#include <vector>
	#include <string>
	#include "ast.hpp"

	#define AS_STRING(arg)		(string((const char*)(arg->getText(arg))->chars))
	#define AS_INTEGER(arg)		(atoi((const char*)(arg->getText(arg))->chars))

	using namespace std;
}


aexpr returns [arith *a]
	:	arg1=aexpr1				{$a = $arg1.a;}
		(	PLUS argN=aexpr1	{$a = new aplus($a, $argN.a);}
		|	MINUS argN=aexpr1	{$a = new aminus($a, $argN.a);}
		)*
	;

aexpr1 returns [arith *a]
	:	arg1=aexpr2				{$a = $arg1.a;}
		(	MUL argN=aexpr2		{$a = new amultiply($a, $argN.a);}
		|	DIV argN=aexpr2		{$a = new adivide($a, $argN.a);}
		)*
	;

aexpr2 returns [arith *a]
	:	arg=aexpr3				{$a = $arg.a;}
	|	MINUS arg=aexpr3		{$a = new anegation($arg.a);}
	;

aexpr3 returns [arith *a]
	:	id=IDENTIFIER								{$a = new variable(AS_STRING(id));}
	|	id=IDENTIFIER LBRACKET arg=aexpr RBRACKET	{$a = new array_element(AS_STRING(id), $arg.a);}
	|	tok=INTEGER									{$a = new constant(AS_INTEGER(tok));}
	|	LPAREN arg=aexpr RPAREN						{$a = $arg.a;}
	;

bexpr returns [bexp *b]
	:	arg1=bexpr1				{$b = $arg1.b;}
		(	OR argN=bexpr1		{$b = new b_or($b, $argN.b);}
		)*
	;

bexpr1 returns [bexp *b]
	:	arg1=bexpr2				{$b = $arg1.b;}
		(	AND argN=bexpr2		{$b = new b_and($b, $argN.b);}
		)*
	;

bexpr2 returns [bexp *b]
	: rel=opr					{$b = $rel.b;}
	| NOT arg=bexpr				{$b = new b_not($arg.b);}
	| TRUE						{$b = new b_true();}
	| FALSE						{$b = new b_false();}
	| LPAREN arg=bexpr RPAREN	{$b = $arg.b;}
	;

opr returns [bexp *b]
	: arg1=aexpr GT arg2=aexpr		{$b = new b_gt($arg1.a, $arg2.a);}
	| arg1=aexpr GE arg2=aexpr		{$b = new b_ge($arg1.a, $arg2.a);}
	| arg1=aexpr LT_fix arg2=aexpr	{$b = new b_lt($arg1.a, $arg2.a);}
	| arg1=aexpr LE arg2=aexpr		{$b = new b_le($arg1.a, $arg2.a);}
	| arg1=aexpr EQ arg2=aexpr		{$b = new b_eq($arg1.a, $arg2.a);}
	| arg1=aexpr NEQ arg2=aexpr		{$b = new b_neq($arg1.a, $arg2.a);}
	;

decl returns [declaration *d]
	: INT id=IDENTIFIER SEMI									{$d = new declaration(AS_STRING(id));}
	| INT id=IDENTIFIER (LBRACKET sz=INTEGER RBRACKET)? SEMI	{$d = new declaration(AS_STRING(id), AS_INTEGER(sz));}
	;

decls returns [symbol_table *st]
@init {vector<declaration*> vd;}
	:	(dN=decl {if($dN.d) vd.push_back($dN.d);})*		{$st = new symbol_table(vd);}
	;

statements returns [statement *s]
@init {vector<statement*> vs;}
	:	(sN=stmt {if($sN.s) vs.push_back($sN.s);})+		{$s = (vs.size()==1) ? vs[0] : new sequence(vs);}	// Wrap only in a sequence statment if more 2 or more statements
	;

stmt returns [statement *s]
	: ss=assignStmt		{$s = $ss.s;}
	| ss=skipStmt		{$s = $ss.s;}
	| ss=readStmt		{$s = $ss.s;}
	| ss=writeStmt		{$s = $ss.s;}
	| ss=ifStmt			{$s = $ss.s;}
	| ss=whileStmt		{$s = $ss.s;}
	;

assignStmt returns [statement *s]
	:	id=IDENTIFIER ASSIGN rhs=aexpr SEMI								{$s = new assignment(new variable(AS_STRING(id)), $rhs.a);}
	|	id=IDENTIFIER LBRACKET idx=aexpr RBRACKET ASSIGN rhs=aexpr SEMI	{$s = new assignment(new array_element(AS_STRING(id), $idx.a), $rhs.a);}
	;

skipStmt returns [statement *s]
	: SKIPP SEMI					{$s = new s_skip();}
	;

readStmt returns [statement *s]
	: READ var=IDENTIFIER SEMI								{$s = new s_read(new variable(AS_STRING(var)));}
	| READ arr=IDENTIFIER LBRACKET idx=aexpr RBRACKET SEMI	{$s = new s_read(new array_element(AS_STRING(arr), $idx.a));}
	;

writeStmt returns [statement *s]
	: WRITE arg=aexpr SEMI			{$s = new s_write($arg.a);}						// It is possible to print out arithmetic expressions, not just single variables
	;

ifStmt returns [statement *s]
	: IF c=bexpr THEN t=statements ELSE f=statements FI		{$s = new s_if($c.b, $t.s, $f.s);}
	| IF c=bexpr THEN t=statements FI						{$s = new s_if($c.b, $t.s, new s_skip());}
	;

whileStmt returns [statement *s]
	: WHILE cond=bexpr DO body=statements OD				{$s = new s_while($cond.b, $body.s);}
	;

prog returns [program *p]
	: PROGRAM ds=decls ss=statements END					{$p = new program($ss.s, $ds.st);}
	;

/////// Lexer rules below ///////

INTEGER
	: ('0' | '1'..'9' '0'..'9'*)
	;

IDENTIFIER
	: LETTER (LETTER|'0'..'9')*
	;

fragment
LETTER
	: 'A'..'Z'
	| 'a'..'z'
	| '_'
	;

WS
	: (' '|'\r'|'\t'|'\u000C'|'\n') { $channel=HIDDEN; }
	;

BLOCK_COMMENT
	:	'/*' ( options{greedy=false;} : . )* '*/' {$channel=HIDDEN;}
	;

LINE_COMMENT
	:	'//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}
	;
